package cn.kuporo.seckill.order.application.place.imnpl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import cn.kuporo.seckill.common.cache.distribute.DistributedCacheService;
import cn.kuporo.seckill.common.cache.local.caffeine.LocalCacheFactory;
import cn.kuporo.seckill.common.constants.MQConstants;
import cn.kuporo.seckill.common.constants.RedisConstants;
import cn.kuporo.seckill.common.exception.ErrorCode;
import cn.kuporo.seckill.common.exception.SeckillException;
import cn.kuporo.seckill.order.application.model.command.SeckillOrderCommand;
import cn.kuporo.seckill.common.model.dto.SeckillGoodsDTO;
import cn.kuporo.seckill.common.model.enums.PlaceOrderType;
import cn.kuporo.seckill.common.model.message.TxMessage;
import cn.kuporo.seckill.common.utils.SnowFlakeFactory;
import cn.kuporo.seckill.dubbo.interfaces.goods.SeckillGoodsDubboService;
import cn.kuporo.seckill.dubbo.interfaces.stock.SeckillStockDubboService;
import cn.kuporo.seckill.mq.MessageSenderService;
import cn.kuporo.seckill.order.application.place.SeckillPlaceOrderService;
import cn.kuporo.seckill.order.domain.model.entity.SeckillOrder;
import cn.kuporo.seckill.order.domain.service.SeckillOrderDomainService;
import com.alibaba.fastjson.JSONObject;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: kuporo
 * @since: 2025/8/3
 * @description:
 */

@Service
@ConditionalOnProperty(name = "place.order.type", havingValue = "bucket")
@RequiredArgsConstructor
@Slf4j
public class SeckillPlaceOrderBucketService implements SeckillPlaceOrderService {
    private static final Cache<Long, Integer> localBucketsQuantityCache = LocalCacheFactory.getLocalCache();
    private SeckillGoodsDubboService seckillGoodsDubboService;
    private final SeckillOrderDomainService seckillOrderDomainService;
    private final MessageSenderService messageSenderService;
    private final DistributedCacheService distributedCacheService;
    @DubboReference
    private SeckillStockDubboService seckillStockDubboService;
    @DubboReference
   
    @Override
    public Long placeOrder(Long userId, SeckillOrderCommand seckillOrderCommand) {
        if (userId == null || seckillOrderCommand == null){
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        // 获取商品
        SeckillGoodsDTO seckillGoods = seckillGoodsDubboService.getSeckillGoods(seckillOrderCommand.getGoodsId(), seckillOrderCommand.getVersion());
        // 检测商品信息
        this.checkSeckillGoods(seckillOrderCommand, seckillGoods);
        // 获取txNo
        long txNo = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
        // 获取分桶数量
        Integer bucketsQuantity = this.getBucketsQuantity(seckillOrderCommand.getGoodsId());
        // 计算用户下单的分桶
        Integer bucketSerialNo = this.getBucketSerialNo(userId, bucketsQuantity);
        // 获取库存分桶数据key
        String stockBucketKey = this.getStockBucketKey(seckillOrderCommand.getGoodsId(), bucketSerialNo);
        // 获取库存编排时加锁的Key
        String stockBucketSuspendKey = this.getStockBucketSuspendKey(seckillOrderCommand.getGoodsId());
        // 获取库存校对key
        String stockBucketAlignKey = this.getStockBucketAlignKey(seckillOrderCommand.getGoodsId());
        // 封装执行Lua脚本的Key
        List<String> keys = Arrays.asList(stockBucketKey, stockBucketSuspendKey, stockBucketAlignKey);
        // 异常标志
        boolean exception = false;
        Long decrementResult = 0L;
        try {
            // 扣减缓存中的分桶库存
            decrementResult = distributedCacheService.decrementBucketStock(keys, seckillOrderCommand.getQuantity());
            this.checkResult(decrementResult);
        } catch (Exception e) {
            exception = true;
            log.error("SeckillPlaceOrderLuaService|下单异常|参数:{}|异常信息:{}", JSONObject.toJSONString(seckillOrderCommand), e.getMessage());
            if (decrementResult == RedisConstants.LUA_BUCKET_STOCK_EXECUTE_SUCCESS){
                Long incrementResult = distributedCacheService.incrementBucketStock(keys, seckillOrderCommand.getQuantity());
                if (incrementResult == null || incrementResult != RedisConstants.LUA_BUCKET_STOCK_EXECUTE_SUCCESS){
                    log.error("placeOrder|恢复预扣减的库存失败|{}|{}", userId, JSONUtil.toJsonStr(seckillOrderCommand), e);
                }
            }
        }
        messageSenderService.sendMessageInTransaction(this.getTxMessage(MQConstants.TOPIC_BUCKET_TX_MSG, txNo, userId, PlaceOrderType.BUCKET, exception, seckillOrderCommand, seckillGoods, bucketSerialNo, seckillOrderCommand.getOrderTaskId()), null);
        
        return txNo;
    }

    @Override
    @Transactional
    public void saveOrderInTransaction(TxMessage txMessage) {
        try {
            Boolean submitTransaction = distributedCacheService.hasKey(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()));
            if (Boolean.TRUE.equals(submitTransaction)){
                log.info("saveOrderInTransaction|已经执行过本地事务|{}", txMessage.getTxNo());
                return;
            }
            // 构建订单
            SeckillOrder seckillOrder = this.buildSeckillOrder(txMessage);
            // 保存订单
            seckillOrderDomainService.saveSeckillOrder(seckillOrder);
            //保存事务日志
            distributedCacheService.put(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()), txMessage.getTxNo(), RedisConstants.TX_LOG_EXPIRE_DAY, TimeUnit.DAYS);
        } catch (Exception e) {
            log.error("saveOrderInTransaction|异常|{}", e.getMessage());
            distributedCacheService.delete(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()));
            this.rollbackCacheStack(txMessage);
            throw e;
        }

    }

    /**
     * 回滚缓存库存
     */
    private void rollbackCacheStack(TxMessage txMessage) {
        //扣减过缓存库存
        if (BooleanUtil.isFalse(txMessage.getException())){
            String luaKey = RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()).concat(RedisConstants.LUA_SUFFIX);
            Long result = distributedCacheService.checkExecute(luaKey, RedisConstants.ORDER_TX_LOG_EXPIRE_SECONDS);
            //已经执行过恢复缓存库存的方法
            if (result.equals(RedisConstants.CHECK_RECOVER_STOCK_HAS_EXECUTE)){
                log.info("handlerCacheStock|已经执行过恢复缓存库存的方法|{}", JSONObject.toJSONString(txMessage));
                return;
            }
            // 回滚分桶库存
            // 获取分桶数量
            Integer bucketsQuantity = this.getBucketsQuantity(txMessage.getGoodsId());
            // 计算用户下单的分桶
            Integer bucketSerialNo = this.getBucketSerialNo(txMessage.getUserId(), bucketsQuantity);
            // 获取库存分桶数据key
            String stockBucketKey = this.getStockBucketKey(txMessage.getGoodsId(), bucketSerialNo);
            // 获取库存编排时加锁的Key
            String stockBucketSuspendKey = this.getStockBucketSuspendKey(txMessage.getGoodsId());
            // 获取库存校对key
            String stockBucketAlignKey = this.getStockBucketAlignKey(txMessage.getGoodsId());
            //封装执行Lua脚本的Key
            List<String> keys = Arrays.asList(stockBucketKey, stockBucketSuspendKey, stockBucketAlignKey);
            Long incrementResult = distributedCacheService.incrementBucketStock(keys, txMessage.getQuantity());
            if (incrementResult == null || incrementResult != RedisConstants.LUA_BUCKET_STOCK_EXECUTE_SUCCESS){
                log.error("rollbackCacheStack|执行回滚分桶库存失败|{}", txMessage);
            }
        }
    }

    /**
     * 检测结果的执行情况
     */
    private void checkResult(Long result) {
        // 分桶库存不存在
        if (result == null || result == RedisConstants.LUA_BUCKET_STOCK_NOT_EXISTS){
            throw new SeckillException(ErrorCode.BUCKET_STOCK_NOT_EXISTS);
        }
        // 库存维护中
        if (result == RedisConstants.LUA_BUCKET_STOCK_SUSPEND){
            throw new SeckillException(ErrorCode.BUCKET_STOCK_SUSPEND);
        }
        // 库存校准中
        if(result == RedisConstants.LUA_BUCKET_STOCK_CALIBRATION){
            throw new SeckillException(ErrorCode.BUCKET_STOCK_ALIGN);
        }
        // 库存不足
        if (result == RedisConstants.LUA_BUCKET_STOCK_NOT_ENOUGH){
            throw new SeckillException(ErrorCode.STOCK_LT_ZERO);
        }
    }

    /**
     * 获取库存编排时加锁的Key
     */
    private String getStockBucketAlignKey(Long goodsId) {
        return RedisConstants.getKey(RedisConstants.GOODS_STOCK_BUCKETS_ALIGN_KEY, String.valueOf(goodsId));
        
    }

    /**
     * 获取库存编排时加锁的Key
     */
    private String getStockBucketSuspendKey(Long goodsId) {
        return RedisConstants.getKey(RedisConstants.GOODS_STOCK_BUCKETS_SUSPEND_KEY, goodsId);
        
    }

    private String getStockBucketKey(Long goodsId, Integer bucketSerialNo) {
        return RedisConstants.getKey(RedisConstants.getKey(RedisConstants.GOODS_BUCKET_AVAILABLE_STOCKS_KEY, goodsId),bucketSerialNo);
    }

    /**
     * 获取分桶编号
     */
    private Integer getBucketSerialNo(Long userId, Integer bucketsQuantity) {
        if (userId == null || bucketsQuantity == null || bucketsQuantity < 0){
            return null;
        }
        //只设置了一个分桶
        if (bucketsQuantity == 1){
            return 0;
        }
        return Math.abs(userId.hashCode()) % bucketsQuantity;
    }

    /**
     * 获取分桶数量
     */
    private Integer getBucketsQuantity(Long goodsId) {
        Integer bucketQuantity = localBucketsQuantityCache.getIfPresent(goodsId);
        if (bucketQuantity != null){
            return bucketQuantity;
        }
        //从分布式缓存中获取分桶数量
        String bucketKey = RedisConstants.getKey(RedisConstants.GOODS_BUCKETS_QUANTITY_KEY, goodsId);
        bucketQuantity = distributedCacheService.getObject(bucketKey, Integer.class);
        if (bucketQuantity != null){
            localBucketsQuantityCache.put(goodsId, bucketQuantity);
        }
        return bucketQuantity;
    }
}
